// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package session

import (
	"encoding/json"
	"net/http"
	"path/filepath"
	"strings"
	"time"

	"chainmaker.org/chainmaker/smarteditor/conf"

	"github.com/88250/gulu"
)

const (
	userExists      = "user exists"
	userCreated     = "user created"
	userCreateError = "user create error"
)

// tempArgs temp args
type tempArgs struct {
	FontFamily            string
	FontSize              string
	GoFmt                 string
	GoBuildArgsForLinux   string
	GoBuildArgsForWindows string
	GoBuildArgsForDarwin  string
	Keymap                string
	Workspace             string
	Username              string
	Locale                string
	Theme                 string
	EditorFontFamily      string
	EditorFontSize        string
	EditorLineHeight      string
	EditorTheme           string
	EditorTabSize         string
}

// Exclusive lock for adding user.
// var addUserMutex sync.Mutex

// PreferenceHandler handles request of preference page.
func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
	httpSession, _ := HTTPSession.Get(r, CookieName)

	if httpSession.IsNew {
		//http.Redirect(w, r, "/login", http.StatusFound)
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
	err := httpSession.Save(r, w)
	if err != nil {
		http.Error(w, "save session error", http.StatusInternalServerError)
		return
	}

	uid := httpSession.Values["uid"].(string)
	user := conf.GetUser(uid)

	// if "GET" == r.Method {
	// 	tmpLinux := user.GoBuildArgsForLinux
	// 	tmpWindows := user.GoBuildArgsForWindows
	// 	tmpDarwin := user.GoBuildArgsForDarwin

	// 	user.GoBuildArgsForLinux = strings.Replace(user.GoBuildArgsForLinux, `"`, `&quot;`, -1)
	// 	user.GoBuildArgsForWindows = strings.Replace(user.GoBuildArgsForWindows, `"`, `&quot;`, -1)
	// 	user.GoBuildArgsForDarwin = strings.Replace(user.GoBuildArgsForDarwin, `"`, `&quot;`, -1)

	// 	model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(user.Locale), "user": user,
	// 		"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version(),
	// 		"locales": i18n.GetLocalesNames(), "gofmts": gulu.Go.GetGoFormats(),
	// 		"themes": conf.GetThemes(), "editorThemes": conf.GetEditorThemes()}
	// 	t, err := template.ParseFiles("views/preference.html")
	// 	if nil != err {
	// 		log.Error(err)
	// 		http.Error(w, err.Error(), http.StatusInternalServerError)
	// 		user.GoBuildArgsForLinux = tmpLinux
	// 		user.GoBuildArgsForWindows = tmpWindows
	// 		user.GoBuildArgsForDarwin = tmpDarwin
	// 		return
	// 	}
	// 	err = t.Execute(w, model)
	// 	if err != nil {
	// 		log.Warnf("preference handler execute failed. err:%v", err)
	// 	}
	// 	user.GoBuildArgsForLinux = tmpLinux
	// 	user.GoBuildArgsForWindows = tmpWindows
	// 	user.GoBuildArgsForDarwin = tmpDarwin
	// 	return
	// }
	handleNoGetRequest(w, r, uid, user)
}

func handleNoGetRequest(w http.ResponseWriter, r *http.Request, uid string, user *conf.User) {
	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)
	args := &tempArgs{}
	if err := json.NewDecoder(r.Body).Decode(args); err != nil {
		log.Error(err)
		result.Code = -1
		return
	}
	log.Debugf("handleNoGetRequest args: %+v ", args)
	if args.FontFamily != "" {
		user.FontFamily = args.FontFamily
	} else {
		user.FontFamily = "Helvetica"
	}
	if args.FontSize != "" {
		user.FontSize = args.FontSize
	} else {
		user.FontSize = "14px"
	}
	if args.GoFmt != "" {
		user.GoFormat = args.GoFmt
	} else {
		user.GoFormat = "gofmt"
	}
	if args.GoBuildArgsForLinux != "" {
		user.GoBuildArgsForLinux = args.GoBuildArgsForLinux
	} else {
		user.GoBuildArgsForLinux = "-i"
	}
	if args.GoBuildArgsForWindows != "" {
		user.GoBuildArgsForWindows = args.GoBuildArgsForWindows
	} else {
		user.GoBuildArgsForWindows = "-i"
	}
	if args.GoBuildArgsForDarwin != "" {
		user.GoBuildArgsForDarwin = args.GoBuildArgsForDarwin
	} else {
		user.GoBuildArgsForDarwin = "-i"
	}
	if args.Keymap != "" {
		user.Keymap = args.Keymap
	} else {
		user.Keymap = "wide"
	}
	if args.Locale != "" {
		user.Locale = args.Locale
	} else {
		user.Locale = conf.Wide.Locale
	}
	if args.Theme != "" {
		user.Theme = args.Theme
	} else {
		user.Theme = "dark"
	}
	if args.EditorFontFamily != "" {
		user.Editor.FontFamily = args.EditorFontFamily
	} else {
		user.Editor.FontFamily = "Consolas, 'Courier New', monospace"
	}
	if args.EditorFontSize != "" {
		user.Editor.FontSize = args.EditorFontSize
	} else {
		user.Editor.FontSize = "14px"
	}
	if args.EditorLineHeight != "" {
		user.Editor.LineHeight = args.EditorLineHeight
	} else {
		user.Editor.LineHeight = "14px"
	}

	if args.EditorTheme != "" {
		user.Editor.Theme = args.EditorTheme
	} else {
		user.Editor.Theme = "monokai"
	}

	if args.EditorTabSize != "" {
		user.Editor.TabSize = args.EditorTabSize
	} else {
		user.Editor.TabSize = "4"
	}
	//conf.UpdateCustomizedConf(uid)
	now := time.Now().UnixNano()
	user.Lived = now
	user.Updated = now
	if user.UpdateAll() {
		result.Code = 0
	} else {
		result.Code = -1
	}
}

// FixedTimeSave saves online users' configurations periodically (1 minute).
//
// Main goal of this function is to save user session content, for restoring session content while user open Wide next time.
func FixedTimeSave() {
	go func() {
		defer gulu.Panic.Recover(nil)

		for _ = range time.Tick(1 * time.Minute) {
			SaveOnlineUsers()
		}
	}()
}

// CanAccess determines whether the user specified by the given user id can access the specified path.
func CanAccess(userId, path string) bool {
	path = filepath.FromSlash(path)
	userWorkspace := conf.GetUserWorkspace(userId)
	workspaces := filepath.SplitList(userWorkspace)

	for _, workspace := range workspaces {
		absPath, err := filepath.Abs(path)
		if err != nil {
			continue
		}
		if absPath == workspace {
			return true
		}
		if len(absPath) > len(workspace) && absPath[len(workspace)] == filepath.Separator && strings.HasPrefix(absPath, workspace) {
			return true
		}
	}
	log.Debugf("access deny. uid:%s, workspace:%s, path:%s", userId, workspaces, path)
	return false
}

// SaveOnlineUsers saves online users' configurations at once.
func SaveOnlineUsers() {
	users := getOnlineUsers()
	for _, u := range users {
		if u.UpdateAll() {
			log.Debugf("saved online user [%s]'s configurations", u.Name)
		}
	}
}

func getOnlineUsers() []*conf.User {
	ret := []*conf.User{}

	uids := map[string]string{} // distinct uid
	for _, s := range WideSessions {
		uids[s.UserID] = s.UserID
	}

	for _, uid := range uids {
		u := conf.GetUser(uid)

		if "playground" == uid { // user [playground] is a reserved mock user
			continue
		}

		if nil == u {
			log.Warnf("Not found user [%s]", uid)

			continue
		}

		ret = append(ret, u)
	}

	return ret
}
